/**
 * Esta clase representa un juego de sudoku. Esta contiene la solucion,
 * la entrada de usuario, el numero seleccioando, la dificultad del juego
 * y metodos para chequear la validacion 
 * de la entrada de usuario.
 */
package sudoku.Modelo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Observable;
import sudoku.Extras.Tripla;

/**
 * @author User
 */
public class Juego extends Observable {
    public int[][] mISolucion;        // Solucion Generada.
    public int[][] mIJuego;           // Juego Generado con entrada de usuario.
    public Tripla[][] mIGuardado;
    public boolean[][] mBComprobar;   // Para Verificar validez del juego.
    private int iNumSeleccionado;     // Numero Seleccionado por Usuario.
    private boolean bAyuda;           // Ayuda si o no.
    private int dificultad;           // Dificultad elejida
    private boolean fin;
    
    /**
     * Constructor
     */
    public Juego() {
        dificultad = 1;
        mISolucion = new int[9][9];
        mIJuego = new int[9][9];
        mBComprobar = new boolean[9][9];
        mIGuardado = new Tripla[9][9];
        
        for (int y = 0; y < 9; y++) {
            for (int x = 0; x < 9; x++){
                mIGuardado[y][x] = new Tripla();
            }
        }
        bAyuda = false;
    }
    
    /**
     * Genera un nuevo sudoku
     * Todos los observadores son notificados, Codigo de Accion = nuevo juego.
     */
    public void nuevoJuego(int dificultad) {
        this.dificultad = dificultad;
        mISolucion = generarSolucion(new int[9][9], 0);
        mIJuego = generarJuego(copiar(mISolucion),dificultad);
        estadoInicial(mIGuardado);
        setChanged();
        notifyObservers("Nuevo");
    }
    
    /**
     * Genera un estado inicial del sudoku.
     * Almacenando en una matriz de 9x9 de triplas, las cuales en cada
     * una contiene: 
     * primer componente el numero, 
     * segundo: 0 si fue juego inicial, 1 si es del usuario.
     * tercero: la solucion de ese campo..
     */
    private void estadoInicial(Tripla[][] mIEstado){
        for (int y = 0; y < 9; y++) {
            for (int x = 0; x < 9; x++){
                mIEstado[y][x].setPri(mIJuego[y][x]);
                mIEstado[y][x].setSeg(0);                
                mIEstado[y][x].setTer(mISolucion[y][x]);
            }
        }  
    }

    public void cargarEstado(Tripla[][] mIEstado){
        for (int y = 0; y < 9; y++) {
            for (int x = 0; x < 9; x++){
                mIJuego[y][x] = mIEstado[y][x].getPri();             
                mISolucion[y][x]= mIEstado[y][x].getTer();
            }
        }
        mIGuardado = mIEstado;
        setChanged();
        notifyObservers("Cargar");
    }
    
    /**
     * Comprueba la entrada de usuario con la solucion y la alamcena en una matriz de valicacion
     * Todos los observadores son notificados, Codigo de Accion = Chequear.
     */
    public boolean comprobarJuego() {
        iNumSeleccionado = 0;
        fin = true;
        for (int y = 0; y < 9; y++) {
            for (int x = 0; x < 9; x++){
                mBComprobar[y][x] = mIJuego[y][x] == mISolucion[y][x];
                fin = fin && (mBComprobar[y][x]);
            }
        }
        if (fin){
            return fin;
        }else{
            setChanged();
            notifyObservers("Chequear");
            return false;
        }
    }

    public void finalizar(){
        setChanged();
        notifyObservers("Solucionar");
    }
    
    /**
     * Setea la ayuda (activada o desactivada)
     * Todos los observadores son notificados, Codigo de Accion = Ayuda.
     * 
     * @param bAyuda True para ayuda activada, False caso contrario.
     */
    public void setAyuda(boolean bAyuda) {
        this.bAyuda = bAyuda;
        setChanged();
        notifyObservers("Ayuda");
    }

    /**
     * Setea el Campo iNumSeleccionado por la entrada de usuario
     * Todos los observadores son notificados, Codigo de Accion = Numero Seleccionado.
     *
     * @param selectedNumber    Number selected by user.
     */
    public void setNumSeleccionado(int iNumSeleccionado) {
        this.iNumSeleccionado = iNumSeleccionado;
        setChanged();
        notifyObservers("NumeroSeleccionado");
    }
    
        /**
     * Setea la dificultad del juego.
     *
     * @param dificultad  dificultad elegida.
     */
    public void setDificultad(int dificultad){
        this.dificultad = dificultad;
        setChanged();
        notifyObservers("Dificultad");
    }
    
    
    /**
     * Devuelve la dificultad del juego.
     *
     * @return dificultad  dificultad del juego.
     */
    public int getDificultad(){
        return dificultad;
    }
    
    
    /**
     * Retorna el numero seleccionado por el usuario.
     *
     * @return el Numero seleccionado por el usuario.
     */
    public int getNumSeleccionado() {
        return iNumSeleccionado;
    }
    
    

    /**
     * Retrona True si la ayuda esta activada, False caso contrario.
     *
     * @return True Ayuda activada, False Caso Contrario.
     */
    public boolean conAyuda() {
        return bAyuda;
    }
    
    /**
     * Retorna si el numero seleccionado es candidato o no en la pocision dada.
     *
     * @param x     X posicion en el juego.
     * @param y     Y posicion en el juego.
     * @return      True si numSeleccionado es candidatoen la posicion dada, false caso contrario.
     */
    public boolean esCandidatoNumSeleccionado(int x, int y) {
        boolean esCandidato = (mIJuego[y][x] == 0 && esCandidato(mIJuego, y, x, iNumSeleccionado));
        return esCandidato; 
    }

    /**
     * Setea un numero dado en una pocicion dada.
     *
     * @param x         Posicion X en el juego.
     * @param y         Posicion Y en el Juego.
     * @param numero    El Numero a Cambiar.
     */
    public void setNumero(int x, int y, int numero) {
        mIJuego[y][x] = numero;
        mIGuardado[y][x].setSeg(1);
        mIGuardado[y][x].setPri(numero);   
    }
    
    
    /**
     * Retorna el numero de la posicion dada.
     *
     * @param x     Posicion X del Juego.
     * @param y     Posicion Y del Juego.
     * @return      El Numero de la posicion.
     */
    public int getNumero(int x, int y) {
        int numero = mIJuego[y][x];
        return numero;
    }
        
    public Tripla[][] getJuegoTemporal() {
        return mIGuardado;
    }
    
    /**
     * Devuelve si la entrada de usuario es valida en la posicion dada .
     *
     * @param x     Posicion X del Juego.
     * @param y     Posicion Y del Juego.     
     * @return      True  si la entrada de usaurio en la posicion dada es valida,
     *              False Caso Contrario.
     */
    public boolean esValida(int x, int y) {
        return mBComprobar[y][x];
    }

    /**
     * Retorna si el numero dado es candidato en la posicion dada.
     * @param juego      Juego para comprobar.
     * @param y         Posicion y del juego.
     * @param x         Posicion x del juego.
     * @param numero    Numero a chequear.
     * @return          True si el numero es candidato, false caso contrario.
     */
    private boolean esCandidato(int[][] juego, int y, int x, int numero) {
        // Verifica por fila Y si es valido
        for (int c = 0; c < 9; c++) {
            if (juego[y][c] == numero)
                return false;
        }
        // Verifica por columna X si es valido.
        for (int f = 0; f < 9; f++) {
            if (juego[f][x] == numero)
                return false;
        }
        // Verifica por el bloque, al que pertenece si es valido
        int x1 = (x/3)*3;
        int y1 = (y/3)*3;
        for (int ff = y1; ff < y1+3; ff++) {
            for (int cc = x1; cc < x1+3; cc++) {
                if (juego[ff][cc] == numero)
                    return false;
            }
        }
        return true;
    }

    /**
     * Devuelve un numero posible (desde una lista) dada la posicion
     * o -1 si no hay numero posible
     *
     * @param juego     Juego a chequear.
     * @param x         X posicion en el juego.
     * @param y         Y posicion en el juego.
     * @param numeros   Lista de numeros.
     * @return          Un numero valido para la posicion dada, -1 si no se consigue numero.
     */
    private int obtenerNumSigPosible(int[][] juego, int y, int x, List<Integer> numeros) {
        while (numeros.size() > 0) {
            int numero = numeros.remove(0);
            if (esCandidato(juego, y, x, numero))
                return numero;
        }
        return -1;
    }
    
    /**
     * Genera Solucion del jeugo.
     *
     * @param Juego     Juego a crear, se le pasa una nueva matriz 'int[9][9]' la primera vez.
     * @param indice     Indice corriente, 0 la primera vez.
     * @return          Solucion de Juego Sudoku .
     */
    private int[][] generarSolucion(int[][] juego, int indice) {
        if (indice > 80)
            return juego;
        
        int x = indice % 9;
        int y = indice / 9;
        List<Integer> numeros = new ArrayList<>();
        //Lista Con Numeros del 1 al 9.
        for (int i = 1; i <= 9; i++) 
            numeros.add(i);        
        Collections.shuffle(numeros);

        while (numeros.size() > 0) {
            int numero = obtenerNumSigPosible(juego, y, x, numeros);
            if (numero == -1)
                return null;
            
            juego[y][x] = numero;
       
            int[][] tmpJuego = generarSolucion(juego, indice + 1);
            if (tmpJuego != null)
                return tmpJuego;
            juego[y][x] = 0;
            
        }
        return null;
    }

    /**
     * Genera Juego Sudoku a partir de una solucion.
     *
     * @param juego      Juego a ser generado. 
     * @return          Juego del sudoku generado.
     */
    private int[][] generarJuego(int[][] juego, int dificultad) {
        List<Integer> posiciones = new ArrayList<>();
        for (int i = 0; i < 81; i++)
            posiciones.add(i);
        Collections.shuffle(posiciones);
        int cantidadNoCeros = 0;
        // Seteamos la cantidad segun la dificultad.
        if (dificultad == 1){
            cantidadNoCeros = 60 ; 
        } else if (dificultad == 2){
            cantidadNoCeros = 45 ;
        }else if (dificultad == 3){
            cantidadNoCeros = 30 ;
        }
 
        while (posiciones.size() > cantidadNoCeros) {
            int posicion = posiciones.remove(0);
            int x = posicion % 9;
            int y = posicion / 9;
            int temp = juego[y][x];
            juego[y][x] = 0;
            if (!esValido(juego))
                juego[y][x] = temp;
        }
                
        return juego;
    }

    /**
     * Comprueba si el juego dado es valido.
     *
     * @param juego     Juego a comprobar.
     * @return          True si juego es valido, false caso contrario.
     */
    private boolean esValido(int[][] juego) {
        return esValido(juego, 0, new int[] {0} );
    }

    /**
     * Comprueba si el juego dado es valido, se llama recursivamente.
     * Deberia dar true si es solucion unica.
     *
     * @param juego                  Juego a comprobar.
     * @param indice                 Indice corriente a comprobar.
     * @param numeroDeSoluciones     Numero de soluciones encontradas.
     * @return                       True si juego es valido, falso caso contrario.
     */
    private boolean esValido(int[][] juego, int indice, int[] numeroDeSoluciones) {
        if (indice > 80)
            return ++numeroDeSoluciones[0] == 1;

        int x = indice % 9;
        int y = indice / 9;

        if (juego[y][x] == 0) {
            List<Integer> numeros = new ArrayList<>();
            for (int i = 1; i <= 9; i++)
                numeros.add(i);
            while (numeros.size() > 0) {
                int numero = obtenerNumSigPosible(juego, y, x, numeros);
                if (numero == -1)
                    break;
                juego[y][x] = numero;
                
                if (!esValido(juego, indice + 1, numeroDeSoluciones)) {
                    juego[y][x] = 0;
                    return false;
                }
                juego[y][x] = 0;
            } //end while
        } 
        else if (!esValido(juego, indice + 1, numeroDeSoluciones))
                return false;
        return true;
    }

    /**
     * Copia el Juego.
     *
     * @param juego      Juego a copiar.
     * @return          Copia del juego.
     */
    private int[][] copiar(int[][] juego) {
        int[][] copia = new int[9][9];
        for (int y = 0; y < 9; y++) {
            System.arraycopy(juego[y], 0, copia[y], 0, 9);
        }
        return copia;
    }

    /*
     * Representacion texto de la matriz.
     *
     * @param juego  juego a ser representado.
     */
    public void toString(int[][] juego) {
        System.out.println();
        for (int y = 0; y < 9; y++) {
            for (int x = 0; x < 9; x++) {
                System.out.print(" " + juego[y][x]);
            }
            System.out.println();
        }
    }
}